package dev42gewinnt.logik;

/**
 * Implementierung des MiniMax-Algorithmus als KI
 * @author Hagen
 *
 */
public class StarkesGehirn implements KI {

	private Satz satz;
	private Spielfeld spielfeld;
	private int suchTiefe;
	public boolean berechnungAbbrechen = false;
	
	/** 
	 * Instantiieren einer neuen MiniMax-KI-Instanz im Satz
	 * @param satz aktueller Satz
	 * @author Hagen
	 */
	StarkesGehirn(Satz satz) {
		this.satz = satz;
		this.suchTiefe = 7; // Voreinstellung, wird ueberschrieben
	}

	/**
	 * Einstellung der Suchtiefe. Bis zu dieser Tiefe wird der Suchbaum maximal aufgebaut.
	 * @param suchTiefe maximale Suchtiefe
	 * @author Hagen
	 */
	public void setSuchTiefe(int suchTiefe) {
		this.suchTiefe = suchTiefe;
	}

	/**
	 * Fuehrt MiniMax-Algorithmus aus und gibt Feldnummer des Feldes
	 * im Spielfeld zurueck, in das gesetzt werden soll
	 * @param zugzeit Maximale Zugzeit, hier allerdings wirkungslos, da Baum bis zu max. Suchtiefe aufgebaut wird
	 * @return Feldnummer des Feldes, in das gesetzt werden soll
	 * @author Hagen
	 */
	@Override
	public int getFeld(int zugzeit) {
		
		int finalMaxWert = Integer.MIN_VALUE; // bester Wert, der erreicht werden kann
		int bestesErgebnis = -1; // Spalte, in die gesetzt werden soll
		
		this.spielfeld = this.satz.getSpielfeld().getClone(); // man braucht
																// immer das
																// aktuelle feld
		int[] moeglicheZuege;
		int tmpWert;
		
		// Zuege herausfinden
		moeglicheZuege = this.spielfeld.getMoeglicheZuege();

		// Zuege durchgehen u Minimax anwenden
		for (int i = 0; i < moeglicheZuege.length; i++) {
			// Pruefen ob Interrupt (= Zeitueberschreitung)
			if (this.berechnungAbbrechen) 
				return -1; // Rueckgabewert wird nicht weiter verwendet
				
			// Zug setzen
			this.spielfeld.setzeZug(true, moeglicheZuege[i]);
			
			
			// MinMax anwenden, um bestes Feld zu bestimmen
			tmpWert = this.minWert(this.suchTiefe - 1, Integer.MIN_VALUE,
					Integer.MAX_VALUE);
			
			// Zug rueckgaengig machen
			this.spielfeld.entferneZug(true, moeglicheZuege[i]);
			
			// ermitteln, ob errechneter wert besser, als bisher ermittelter und ggf. bestes Ergebnis aktualisieren
			if (tmpWert >= finalMaxWert) {
				finalMaxWert = tmpWert;
				bestesErgebnis = moeglicheZuege[i];
			}
		}
		return bestesErgebnis;
	}

	/**
	 * Max-Funktion des Minimax-Algorithmus (Simulation von Zuegen des eigenen Spielers)
	 * @param tiefe Resttiefe
	 * @param alpha Alpha-Wert
	 * @param beta Beta-Wert
	 * @return Bewertung
	 * @author Hagen
	 */
	private int maxWert(int tiefe, int alpha, int beta) {
		int zugWert;
		int[] moeglicheZuege;
		
		// Pruefen ob Interrupt (= Zeitueberschreitung)
		if (this.berechnungAbbrechen) 
			return -1; // Rueckgabewert wird nicht weiter verwendet
		
		// Moegliche Zuege ermitteln
		moeglicheZuege = this.spielfeld.getMoeglicheZuege();

		// Pruefen ob max Tiefe erreicht oder keine Zuege mehr moeglich und ggf. abbrechen
		if ((tiefe == 0) || (moeglicheZuege.length < 1)) {
			return (this.spielfeld.getBewertung(true) - this.spielfeld
					.getBewertung(false));
		}

		// Alle moeglichen Zuege durchgehen
		for (int i = 0; i < moeglicheZuege.length; i++) {
			// Zug durchfuehren
			this.spielfeld.setzeZug(true, moeglicheZuege[i]);
			
			// Pruefen ob Gewinnsituation erreicht
			if(this.spielfeld.pruefeSieg(true)) {
				this.spielfeld.entferneZug(true, moeglicheZuege[i]);
				return (BitboardSpielfeld.BEWERTUNG_VIERERKOMBINATION / (this.suchTiefe/tiefe)); // Gewichtung -> in tieferer Ebene auftretende Gewinnsituatioen sind weniger interessant
			}
			zugWert = this.minWert(tiefe - 1, alpha, beta);

			this.spielfeld.entferneZug(true, moeglicheZuege[i]);

			if (zugWert >= beta) {
				return beta;
			}
			if (zugWert > alpha) {
				alpha = zugWert;
			}
		}
		return alpha;
	}
	
	/**
	 * Min-Funktion des Minimax-Algorithmus (Simulation von Zuegen des gegnerischen Spielers)
	 * @param tiefe Resttiefe
	 * @param alpha Alpha-Wert
	 * @param beta Beta-Wert
	 * @return Bewertung
	 * @author Hagen
	 */
	private int minWert(int tiefe, int alpha, int beta) {
		int zugWert;
		int[] moeglicheZuege;
		
		// Pruefen ob Interrupt (= Zeitueberschreitung)
		if (this.berechnungAbbrechen)  
			return -1; // Rueckgabewert wird nicht weiter verwendet

		// Moegliche Zuege ermitteln
		moeglicheZuege = spielfeld.getMoeglicheZuege();

		// Pruefen ob max Tiefe erreicht oder keine Zuege mehr moeglich und ggf. abbrechen
		if ((tiefe == 0) || (moeglicheZuege.length < 1)) {
			return (this.spielfeld.getBewertung(true) - this.spielfeld
					.getBewertung(false));
		}

		// Alle moeglichen Zuege durchgehen
		for (int i = 0; i < moeglicheZuege.length; i++) {
			// Zug durchfuehren
			this.spielfeld.setzeZug(false, moeglicheZuege[i]);

			// Pruefen ob Verlustsituation erreicht
			if(this.spielfeld.pruefeSieg(false)) {
				this.spielfeld.entferneZug(false, moeglicheZuege[i]);
				return (-1*(BitboardSpielfeld.BEWERTUNG_VIERERKOMBINATION / (this.suchTiefe/tiefe))); // Gewichtung -> in tieferer Ebene auftretende Verlustsituationen sind weniger interessant
			}
			
			zugWert = this.maxWert(tiefe - 1, alpha, beta);

			this.spielfeld.entferneZug(false, moeglicheZuege[i]);

			if (zugWert <= alpha) {
				return alpha;
			}
			if (zugWert < beta) {
				beta = zugWert;
			}
		}
		return beta;
	}

}